package org.os.framework.crypto.algorithm;

import java.math.BigInteger;
import java.security.SecureRandom;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.agreement.SM2KeyExchange;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.params.SM2KeyExchangePrivateParameters;
import org.bouncycastle.crypto.params.SM2KeyExchangePublicParameters;
import org.bouncycastle.util.encoders.Hex;
import org.os.framework.crypto.engine.Sm2EcdsaSigner;
import org.os.framework.crypto.engine.Sm2Enginer;
import org.os.framework.crypto.model.AbstractStatic;
import org.os.framework.crypto.model.Crypto;
import org.os.framework.crypto.model.Exchange;
import org.os.framework.crypto.model.Signature;

import cn.hutool.core.lang.Assert;
import lombok.SneakyThrows;

/**
 * SM2
 * @author
 */
public class Sm2 extends AbstractStatic implements Crypto<String>, Signature, Exchange {

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private String privateKey;
        private String publicKey;
        private Digest digest;

        public Builder privateKey(String privateKey) {
            this.privateKey = privateKey;
            return this;
        }

        public Builder publicKey(String publicKey) {
            this.publicKey = publicKey;
            return this;
        }

        public Builder digest(Digest digest) {
            this.digest = digest;
            return this;
        }

        public Sm2 build() {
            return new Sm2(this.privateKey, this.publicKey, this.digest);
        }

    }

    private static final String SP = "00c9799f6d7c62ec839b56e1ee7f86a3372769c9e348d1c0adc31987f46aa99a";
    private static final String SK = "04e81b76ae75a7b48795ab7650d3fedea74ece6f5d979c6a30526b822b06c0b9dcf2e0850deb98551246c66d4dc4cede03b47553e6a270c9d571d8118f676879cb";

    private static final String EC_GEN_PARAMETER_SPEC = "sm2p256v1";

    private static final String PREFIX = "04";
    private final X9ECParameters curve = GMNamedCurves.getByName(EC_GEN_PARAMETER_SPEC);
    private final ECDomainParameters domain = new ECDomainParameters(
            curve.getCurve(),
            curve.getG(),
            curve.getN());

    private byte[] privateKey;
    private byte[] publicKey;
    private Digest digest;

    public Sm2(String privateKey, String publicKey, Digest digest) {
        if (null != privateKey) {
            privateKey(privateKey);
        }
        if (null != publicKey) {
            publicKey(publicKey);
        }
        if (null != digest) {
            digest(digest);
        }
    }

    private void privateKey(String privateKey) {
        Assert.notBlank(privateKey, "请输入私钥");
        this.privateKey = Hex.decode(privateKey);
    }

    private void publicKey(String publicKey) {
        Assert.notBlank(publicKey, "请输入公钥");
        this.publicKey = Hex.decode(publicKey);
    }

    private void digest(Digest digest) {
        this.digest = digest == null ? new SM3Digest() : digest;
    }

    @SneakyThrows
    @Override
    public String encrypt(String original) {
        Sm2Enginer engine = new Sm2Enginer();
        engine.init(true, new ParametersWithRandom(
                new ECPublicKeyParameters(curve.getCurve().decodePoint(publicKey), domain),
                new SecureRandom()));
        byte[] bytes = engine.processBlock(original.getBytes(), 0, original.getBytes().length);
        return Hex.toHexString(bytes);
    }

    @SneakyThrows
    @Override
    public String decrypt(String encrypted) {
        byte[] bytes = encrypted.startsWith(PREFIX) ? Hex.decode(encrypted.substring(2)) : Hex.decode(encrypted);
        Sm2Enginer engine = new Sm2Enginer();
        engine.init(false, new ECPrivateKeyParameters(
                new BigInteger(Hex.toHexString(privateKey), 16),
                domain));
        byte[] data = engine.processBlock(bytes, 0, bytes.length);
        return new String(data);
    }

    @Override
    public String sign(String original) {
        byte[] data = original.getBytes();
        Sm2EcdsaSigner signer = new Sm2EcdsaSigner(digest);
        signer.init(
                true,
                new ParametersWithRandom(
                        new ECPrivateKeyParameters(new BigInteger(Hex.toHexString(privateKey), 16), domain),
                        new SecureRandom()));
        BigInteger[] rs = signer.generateSignature(data);
        BigInteger r = rs[0];
        BigInteger s = rs[1];
        byte[] bytes = new byte[r.toByteArray().length + s.toByteArray().length];
        System.arraycopy(r.toByteArray(), 0, bytes, 0, r.toByteArray().length);
        System.arraycopy(s.toByteArray(), 0, bytes, r.toByteArray().length, s.toByteArray().length);
        return Hex.toHexString(bytes);
    }

    @Override
    public boolean verify(String original, String signature) {
        byte[] data = original.getBytes();
        Assert.isTrue(128 == signature.length(), "仅支持128位长度的签名");
        BigInteger r = new BigInteger(signature.substring(0, 64), 16);
        BigInteger s = new BigInteger(signature.substring(64), 16);
        Sm2EcdsaSigner signer = new Sm2EcdsaSigner(digest);
        signer.init(
                false,
                new ECPublicKeyParameters(curve.getCurve().decodePoint(publicKey), domain));
        return signer.verifySignature(data, r, s);
    }

    @Override
    public String confer(String publicKey) {
        Assert.notNull(publicKey, "请输入待交换的公钥");
        SM2KeyExchange exchange = new SM2KeyExchange();
        SM2KeyExchangePrivateParameters sm2KeyExchangePrivateParameters = new SM2KeyExchangePrivateParameters(
                true,
                new ECPrivateKeyParameters(new BigInteger(Hex.toHexString(privateKey), 16), domain),
                new ECPrivateKeyParameters(new BigInteger(SP, 16), domain));

        SM2KeyExchangePublicParameters sm2KeyExchangePublicParameters = new SM2KeyExchangePublicParameters(
                new ECPublicKeyParameters(curve.getCurve().decodePoint(Hex.decode(publicKey)), domain),
                new ECPublicKeyParameters(curve.getCurve().decodePoint(Hex.decode(SK)), domain));

        // 交换密钥
        exchange.init(sm2KeyExchangePrivateParameters);
        byte[] bytes = exchange.calculateKey(128, sm2KeyExchangePublicParameters);
        return Hex.toHexString(bytes);
    }

    @Override
    public String share(String publicKey) {
        Assert.notNull(publicKey, "请输入待分享的公钥");
        SM2KeyExchange exchange = new SM2KeyExchange();
        SM2KeyExchangePrivateParameters sm2KeyExchangePrivateParameters = new SM2KeyExchangePrivateParameters(
                false,
                new ECPrivateKeyParameters(new BigInteger(Hex.toHexString(privateKey), 16), domain),
                new ECPrivateKeyParameters(new BigInteger(SP, 16), domain));

        SM2KeyExchangePublicParameters sm2KeyExchangePublicParameters = new SM2KeyExchangePublicParameters(
                new ECPublicKeyParameters(curve.getCurve().decodePoint(Hex.decode(publicKey)), domain),
                new ECPublicKeyParameters(curve.getCurve().decodePoint(Hex.decode(SK)), domain));

        // 交换密钥
        exchange.init(sm2KeyExchangePrivateParameters);
        byte[] bytes = exchange.calculateKey(128, sm2KeyExchangePublicParameters);
        return Hex.toHexString(bytes);
    }

}
